home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 2000 November: Tool Chest / Dev.CD Nov 00 TC Disk 2.toast / pc / sample code / quicktime / quicktime text / qttext / qttext.c < prev    next >
Encoding:
Text File  |  2000-09-28  |  31.8 KB  |  1,099 lines

  1. //////////
  2. //
  3. //    File:        QTText.c
  4. //
  5. //    Contains:    QuickTime text media handler sample code.
  6. //
  7. //    Written by:    Tim Monroe
  8. //                parts based on QTTextSample code by Nick Thompson (see develop, issue 20).
  9. //
  10. //    Copyright:    © 1995-1998 by Apple Computer, Inc., all rights reserved.
  11. //
  12. //    Change History (most recent first):
  13. //
  14. //       <9>         06/14/99    rtm        added a bunch more chapter track utilities
  15. //       <8>         06/11/99    rtm        added QTText_GetChapterTrackForTrack
  16. //       <7>         06/10/98    rtm        general clean-up; in QTText_AddTextTrack, set the time scale of the
  17. //                                    text track media to that of the movie
  18. //       <6>         06/08/98    rtm        fixed text track duration calculations
  19. //       <5>         06/05/98    rtm        fixed track geometry calculations in QTText_AddTextTrack
  20. //       <4>         06/03/98    rtm        added QTText_AddTextTrack and QTText_RemoveIndTextTrack; borrowed
  21. //                                    QTText_CopyCStringToPascal from source file CGlue.c
  22. //       <3>         05/29/98    rtm        added chapter track enabling/disabling
  23. //       <2>         04/08/98    rtm        added text offset handling, so we can now search within the current sample;
  24. //                                    added QTText_EditText to allow editing a sample's text; added compile flag
  25. //                                    to use MovieSearchText instead of TextMediaFindNextText (see Note 3)
  26. //       <1>         04/07/98    rtm        first file; conversion to personal coding style; updated to latest headers;
  27. //                                    got basic searching working on Mac and Windows
  28. //     
  29. //    This source code shows how to do searches on text media, how to use a simple text procedure to retrieve
  30. //    the text from a text media sample, and how to edit that text. It also shows how to convert a text track
  31. //    into a chapter track (and vice versa), and how to add (and remove) text tracks to (and from) a movie.
  32. //
  33. // NOTES:
  34. //
  35. // *** (1) ***
  36. // This code is based in part on the code provided with the develop article on QuickTime text by Nick Thompson
  37. // (issue 20). Make sure to read that article for complete details on the techniques employed here. I have
  38. // taken the liberty of reworking that code as necessary to make it run on Windows platforms and to bring it 
  39. // into line with the other QuickTime code samples.
  40. //
  41. // *** (2) ***
  42. // The editted text does NOT use the font, size, color, justification, or background color of the text
  43. // it replaces. It would be straightforward to add this capability. See the develop article mentioned above for
  44. // code that does all these things.
  45. //
  46. // *** (3) ***
  47. // The Movie Toolbox provides two different functions that you can use to search for text in a text track: 
  48. // TextMediaFindNextText (originally called FindNextText) and MovieSearchText. TextMediaFindNextText inspects
  49. // only a specified track, while MovieSearchText can search all text tracks in a specified movie. Moreover,
  50. // MovieSearchText will automatically go to and highlight the found text; these operations must be done manually
  51. // if you're using TextMediaFindNextText. This sample code illustrates BOTH of these functions; you determine
  52. // which is used by setting the USE_MOVIESEARCHTEXT compiler flag in QTText.h.
  53. //
  54. //////////
  55.  
  56. #include "QTText.h"
  57.  
  58.  
  59. //////////
  60. //
  61. // global variables
  62. //
  63. //////////
  64.  
  65. Boolean                        gSearchForward = true;                // do we search forward or backward?
  66. Boolean                        gSearchWrap = true;                    // do we wrap around when searching?
  67. Boolean                        gSearchWithCase = false;            // is the search case sensitive?
  68. Str255                        gSearchText;                        // the text we're searching for
  69. Str255                        gSampleText;                        // the text of the current text media sample
  70. long                        gOffset;                            // offset of current found text within sample
  71.  
  72. extern ModalFilterUPP        gModalFilterUPP;
  73.  
  74.  
  75. //////////
  76. //
  77. // QTText_InitWindowData
  78. // Initialize any window-specific data for the text media handler.
  79. //
  80. //////////
  81.  
  82. ApplicationDataHdl QTText_InitWindowData (WindowObject theWindowObject)
  83. {
  84.     ApplicationDataHdl        myAppData = NULL;
  85.     Track                    myTrack = NULL;
  86.     MediaHandler            myHandler = NULL;
  87.  
  88.     myAppData = (ApplicationDataHdl)NewHandleClear(sizeof(ApplicationDataRecord));
  89.     if (myAppData != NULL) {
  90.     
  91.         myTrack = GetMovieIndTrackType((**theWindowObject).fMovie, 1, TextMediaType, movieTrackMediaType | movieTrackEnabledOnly);
  92.         if (myTrack != NULL) {
  93.             // load the entire text track into RAM
  94.             LoadTrackIntoRam(myTrack, 0L, GetTrackDuration(myTrack), 0L);
  95.  
  96.             // set the text handling procedure
  97.             myHandler = GetMediaHandler(GetTrackMedia(myTrack));
  98.             if (myHandler != NULL)
  99.                 TextMediaSetTextProc(myHandler, NewTextMediaProc(QTText_TextProc), (long)theWindowObject);
  100.         }
  101.     
  102.         // remember the text track and media handler
  103.         (**myAppData).fMovieHasText = (myTrack != NULL);
  104.         (**myAppData).fTextIsChapter = QTText_TrackTypeHasAChapterTrack((**theWindowObject).fMovie, VideoMediaType);
  105.         (**myAppData).fTextTrack = myTrack;
  106.         (**myAppData).fTextHandler = myHandler;
  107.     }
  108.     
  109.     return(myAppData);
  110. }
  111.  
  112.  
  113. //////////
  114. //
  115. // QTText_DumpWindowData
  116. // Get rid of any window-specific data for the text media handler.
  117. //
  118. //////////
  119.  
  120. void QTText_DumpWindowData (WindowObject theWindowObject)
  121. {
  122.     ApplicationDataHdl        myAppData = NULL;
  123.         
  124.     myAppData = (ApplicationDataHdl)GetAppDataFromWindowObject(theWindowObject);
  125.     if (myAppData != NULL)
  126.         DisposeHandle((Handle)myAppData);
  127. }
  128.  
  129.  
  130. //////////
  131. //
  132. // QTText_SetSearchText
  133. // Let the user specify the text to be searched for.
  134. //
  135. //////////
  136.  
  137. void QTText_SetSearchText (void)
  138. {
  139.     DialogPtr        myDialog;
  140.     short            myItem;
  141.     short            myType;
  142.     Handle            myItemHandle;
  143.     Rect            myRect;
  144.     
  145.     // get the dialog that lets the user specify the search text
  146.     myDialog = GetNewDialog(kTextDialogID, NULL, (WindowPtr)-1);
  147.     if (myDialog == NULL)
  148.         goto bail;
  149.  
  150.     SetDialogDefaultItem(myDialog, kTextOKIndex);
  151.     
  152.     // set the current search text into the edittext field
  153.     GetDialogItem(myDialog, kTextTextEditIndex, &myType, &myItemHandle, &myRect);
  154.     SetDialogItemText(myItemHandle, gSearchText);
  155.     SelectDialogItemText(myDialog, kTextTextEditIndex, 0, 32767);    
  156.         
  157.     // now show the dialog
  158.     MacShowWindow(myDialog);
  159.     MacSetPort((GrafPtr)myDialog);
  160.     
  161.     do {
  162.         ModalDialog(gModalFilterUPP, &myItem);
  163.     } while (myItem != kTextOKIndex);
  164.     
  165.     // get the text in the edittext field
  166.     GetDialogItemText(myItemHandle, gSearchText);
  167.         
  168. bail:
  169.     if (myDialog != NULL)
  170.         DisposeDialog(myDialog);
  171. }
  172.  
  173.  
  174. //////////
  175. //
  176. // QTText_FindText
  177. // Find the specified string in the (first) text track of the specified window object.
  178. //
  179. //////////
  180.  
  181. void QTText_FindText (WindowObject theWindowObject, Str255 theText) 
  182. {
  183.     ApplicationDataHdl        myAppData;
  184.     Movie                    myMovie;
  185.     MediaHandler            myHandler = NULL;
  186.     MovieController            myMC = NULL;
  187.     long                    myFlags = 0L;
  188.     TimeValue                myTimeValue;
  189.     OSErr                    myErr = noErr;
  190.         
  191.     myAppData = (ApplicationDataHdl)GetAppDataFromWindowObject(theWindowObject);
  192.     if (myAppData == NULL)
  193.         goto bail;
  194.         
  195.     myMC = (**theWindowObject).fController;
  196.     myMovie = (**theWindowObject).fMovie;
  197.     myHandler = (**myAppData).fTextHandler;
  198.     
  199.     // set the search features
  200.     myFlags = findTextUseOffset;
  201.  
  202.     if (!gSearchForward)
  203.         myFlags |= findTextReverseSearch;
  204.         
  205.     if (gSearchWrap)
  206.         myFlags |= findTextWrapAround;
  207.     
  208.     if (gSearchWithCase)
  209.         myFlags |= findTextCaseSensitive;
  210.  
  211. #if USE_MOVIESEARCHTEXT
  212.     //////////
  213.     //
  214.     // METHOD ONE: Use MovieSearchText, your one-stop, find-the-text-and-do-the-right-thing function.
  215.     //
  216.     //////////
  217.     
  218.     myFlags |= searchTextEnabledTracksOnly;
  219.     myTimeValue = GetMovieTime(myMovie, NULL);
  220.     
  221.     myErr = MovieSearchText(myMovie, (Ptr)(&theText[1]), theText[0], myFlags, NULL, &myTimeValue, &gOffset);
  222.     if (myErr != noErr)
  223.         DoBeep();        // if the desired string wasn't found, beep
  224. #else
  225.     //////////
  226.     //
  227.     // METHOD TWO: Use TextMediaFindNextText. Here, you need to explicitly go to the found text and select it. 
  228.     //
  229.     //////////
  230.  
  231.     if (myHandler != NULL) {
  232.         TimeValue    myFoundTime, myFoundDuration;
  233.         TimeRecord    myNewTime;
  234.         RGBColor    myColor;
  235.         
  236.         myColor.red = myColor.green = myColor.blue = 0x8000;    // grey
  237.         
  238.         // search for the specified text
  239.         myErr = TextMediaFindNextText(myHandler, (Ptr)(&theText[1]), theText[0], myFlags, GetMovieTime(myMovie, NULL), &myFoundTime, &myFoundDuration, &gOffset);    
  240.         if (myFoundTime != -1) {
  241.             // convert the TimeValue to a TimeRecord
  242.             myNewTime.value.hi = 0;
  243.             myNewTime.value.lo = myFoundTime;
  244.             myNewTime.scale = GetMovieTimeScale(myMovie);
  245.             myNewTime.base = NULL;
  246.                         
  247.             // go to the found text    
  248.             MCDoAction(myMC, mcActionGoToTime, &myNewTime);
  249.  
  250.             // highlight the text
  251.             TextMediaHiliteTextSample(myHandler, myFoundTime, gOffset, gOffset + theText[0], &myColor);
  252.             
  253.         } else {
  254.             // if the desired string wasn't found, beep
  255.             DoBeep();
  256.         }
  257.     }
  258. #endif // USE_MOVIESEARCHTEXT
  259.  
  260.     // update the current offset, if we're searching forward
  261.     if (gSearchForward && (myErr == noErr))
  262.         gOffset += theText[0];
  263.     
  264. bail:
  265.     ;
  266. }
  267.  
  268.  
  269. //////////
  270. //
  271. // QTText_EditText
  272. // Edit the text in the current sample of the (first) text track of the specified window object.
  273. //
  274. //////////
  275.  
  276. void QTText_EditText (WindowObject theWindowObject) 
  277. {
  278.     ApplicationDataHdl        myAppData = NULL;
  279.     Movie                    myMovie;
  280.     Track                    myTrack;
  281.     Media                    myMedia;
  282.     MediaHandler            myHandler = NULL;
  283.     DialogPtr                myDialog = NULL;
  284.     short                    myItem;
  285.     short                    myType;
  286.     Handle                    myItemHandle = NULL;
  287.     Rect                    myRect;
  288.     OSErr                    myErr = noErr;
  289.         
  290.     // get the movie and related stuff    
  291.     myAppData = (ApplicationDataHdl)GetAppDataFromWindowObject(theWindowObject);
  292.     if (myAppData == NULL)
  293.         goto bail;
  294.         
  295.     myMovie = (**theWindowObject).fMovie;
  296.     myTrack = (**myAppData).fTextTrack;
  297.     myMedia = GetTrackMedia(myTrack);
  298.     myHandler = (**myAppData).fTextHandler;
  299.  
  300.     // get the dialog that lets the user specify the text for the current sample
  301.     myDialog = GetNewDialog(kEditDialogID, NULL, (WindowPtr)-1);
  302.     if (myDialog == NULL)
  303.         goto bail;
  304.  
  305.     SetDialogDefaultItem(myDialog, kEditOKIndex);
  306.     SetDialogCancelItem(myDialog, kEditCancelIndex);
  307.     
  308.     // set the current sample text into the edittext field
  309.     GetDialogItem(myDialog, kEditTextEditIndex, &myType, &myItemHandle, &myRect);
  310.     SetDialogItemText(myItemHandle, gSampleText);
  311.     SelectDialogItemText(myDialog, kEditTextEditIndex, 0, 32767);    
  312.  
  313.     // now show the dialog
  314.     MacShowWindow(myDialog);
  315.     MacSetPort((GrafPtr)myDialog);
  316.     
  317.     do {
  318.         ModalDialog(gModalFilterUPP, &myItem);
  319.     } while ((myItem != kEditOKIndex) && (myItem != kEditCancelIndex));
  320.     
  321.     // if the user hit the OK button, save the text and update the text sample 
  322.     if (myItem == kEditOKIndex) {
  323.         Fixed            myWidth;
  324.         Fixed            myHeight;
  325.         Rect            myBounds;    
  326.         TimeValue        myMovieTime;
  327.         TimeValue        mySampleTime;            
  328.         TimeValue        myDuration;
  329.         TimeValue        myMediaSampleDuration;
  330.         TimeValue        myMediaSampleStartTime;
  331.         TimeValue        myMediaCurrentTime;
  332.         TimeValue        myInterestingTime;
  333.         long            myMediaSampleIndex;
  334.  
  335.         // get the text in the edittext field
  336.         GetDialogItemText(myItemHandle, gSampleText);
  337.         
  338.         // install that text as the current text media sample
  339.  
  340.         // first, we need to find the start and duration for this media sample;
  341.         // get the current movie time and the time in media time of the current sample
  342.         myMovieTime = GetMovieTime(myMovie, NULL);
  343.         if (myMovieTime < 0)
  344.             goto bail;
  345.             
  346.         myMediaCurrentTime = TrackTimeToMediaTime(myMovieTime, myTrack);
  347.                 
  348.         // get detailed information about the start and duration of the current sample        
  349.         MediaTimeToSampleNum(    myMedia, 
  350.                                 myMediaCurrentTime, 
  351.                                 &myMediaSampleIndex, 
  352.                                 &myMediaSampleStartTime,
  353.                                 &myMediaSampleDuration);
  354.                                 
  355.         // see where this text starts
  356.         GetTrackNextInterestingTime(myTrack, nextTimeEdgeOK | nextTimeMediaSample, myMovieTime, -fixed1, &myInterestingTime, NULL);        
  357.         myMovieTime = myInterestingTime;                    
  358.         GetTrackNextInterestingTime(myTrack, nextTimeEdgeOK | nextTimeMediaSample, myMovieTime, fixed1, &myInterestingTime, &myDuration);
  359.                             
  360.         myErr = BeginMediaEdits(myMedia);
  361.         if (myErr != noErr) 
  362.             goto bail;
  363.                 
  364.         // delete the existing text            
  365.         myErr = DeleteTrackSegment(myTrack, myInterestingTime, myDuration);
  366.         if (myErr != noErr) 
  367.             goto bail;
  368.             
  369.         // get the track bounds
  370.         GetTrackDimensions(myTrack, &myWidth, &myHeight);
  371.         myBounds.top = 0;
  372.         myBounds.left = 0;
  373.         myBounds.right = Fix2Long(myWidth);
  374.         myBounds.bottom = Fix2Long(myHeight);    
  375.  
  376.         // write out the new data to the media
  377.         myErr = TextMediaAddTextSample(    myHandler, 
  378.                                         (Ptr)(&gSampleText[1]), 
  379.                                         gSampleText[0],
  380.                                         0,
  381.                                         0,
  382.                                         0,
  383.                                         NULL, 
  384.                                         NULL, 
  385.                                         teCenter,
  386.                                         &myBounds, 
  387.                                         dfClipToTextBox, 
  388.                                         0, 
  389.                                         0, 
  390.                                         0, 
  391.                                         NULL, 
  392.                                         myMediaSampleDuration, 
  393.                                         &mySampleTime);
  394.                                 
  395.         if (myErr != noErr) 
  396.             goto bail;
  397.             
  398.         // insert the new media into the track
  399.         InsertMediaIntoTrack(myTrack, myInterestingTime, mySampleTime, myMediaSampleDuration, fixed1);
  400.         EndMediaEdits(myMedia);
  401.         
  402.         // stamp the movie as dirty
  403.         (**theWindowObject).fDirty = true;
  404.     }
  405.     
  406. bail:
  407.     if (myDialog != NULL)
  408.         DisposeDialog(myDialog);
  409. }
  410.  
  411.  
  412. //////////
  413. //
  414. // QTText_TextProc
  415. // This function is called whenever a new text sample is about to be displayed.
  416. // We'll use it to grab the text of the current text sample.
  417. //
  418. // NOTE: The theRefCon parameter is a handle to a window object record.
  419. //
  420. //////////
  421.  
  422. PASCAL_RTN OSErr QTText_TextProc (Handle theText, Movie theMovie, short *theDisplayFlag, long theRefCon)
  423. {
  424.     char            *myTextPtr = NULL;
  425.     short            myTextSize;
  426.     short            myIndex;
  427.     
  428.     // on entry to this function, theText is a handle to the formatted text,
  429.     // which is a big-endian 16-bit length word followed by the text itself
  430.     myTextSize = EndianU16_BtoN(*(short *)(*theText));
  431.     myTextPtr = (char *)(*theText + sizeof(short));
  432.  
  433.     // copy the text into our global variable
  434.     for (myIndex = 1; myIndex <= myTextSize; myIndex++, myTextPtr++)
  435.         gSampleText[myIndex] = *myTextPtr;
  436.  
  437.     gSampleText[0] = myTextSize;
  438.  
  439.     // ask for the default text display
  440.     *theDisplayFlag = txtProcDefaultDisplay;
  441.  
  442.     return(noErr);
  443. }
  444.  
  445.  
  446. ///////////////////////////////////////////////////////////////////////////////////////////////////////////
  447. //
  448. // Text track utilities.
  449. //
  450. // Use these functions to manipulate text tracks in a movie.
  451. //
  452. ///////////////////////////////////////////////////////////////////////////////////////////////////////////
  453.  
  454. //////////
  455. //
  456. // QTText_AddTextTrack
  457. // Add a text track containing some text data to a movie; return the new track to the caller.
  458. //
  459. // The theStrings parameter is an array of C strings; each element is the text for a certain span of frames
  460. // of the movie. The theFrames parameter is an array of frame counts; each of these counts determines how many
  461. // frames the corresponding text element in the theStrings array applies to; the sum of all the values in this
  462. // array should be equal to the total number of frames in the movie.
  463. //
  464. // If the isChapterTrack parameter is true, the text track is set to be a chapter track, attached to the (first)
  465. // track of type theType.
  466. //
  467. //////////
  468.  
  469. Track QTText_AddTextTrack (Movie theMovie, char *theStrings[], short theFrames[], short theNumFrames, OSType theType, Boolean isChapterTrack)
  470. {
  471.     Track                myTypeTrack = NULL;
  472.     Track                myTextTrack = NULL;
  473.     Media                myMedia;
  474.     MediaHandler        myHandler = NULL;
  475.     TimeScale            myTimeScale;
  476.     MatrixRecord        myMatrix;
  477.     Fixed                myWidth;
  478.     Fixed                myHeight;
  479.     OSErr                myErr = noErr;
  480.  
  481.     //////////
  482.     //
  483.     // find the target track
  484.     //
  485.     //////////
  486.  
  487.     // get the (first) track of the specified type; this track determines the width of the new text track
  488.     // and (if isChapterTrack is true) is the target of the new chapter track
  489.     myTypeTrack = GetMovieIndTrackType(theMovie, 1, theType, movieTrackMediaType);
  490.     if (myTypeTrack == NULL)
  491.         goto bail;
  492.     
  493.     // get the dimensions of the target track
  494.     GetTrackDimensions(myTypeTrack, &myWidth, &myHeight);
  495.     myTimeScale = GetMediaTimeScale(GetTrackMedia(myTypeTrack));
  496.     
  497.     //////////
  498.     //
  499.     // create the text track and media
  500.     //
  501.     //////////
  502.  
  503.     myTextTrack = NewMovieTrack(theMovie, myWidth, FixRatio(kTextTrackHeight, 1), kNoVolume);
  504.     myMedia = NewTrackMedia(myTextTrack, TextMediaType, myTimeScale, NULL, 0);
  505.     myHandler = GetMediaHandler(myMedia);
  506.     
  507.     //////////
  508.     //
  509.     // figure out the text track geometry
  510.     //
  511.     //////////
  512.     
  513.     GetTrackMatrix(myTextTrack, &myMatrix);
  514.     TranslateMatrix(&myMatrix, 0, myHeight);
  515.     
  516.     SetTrackMatrix(myTextTrack, &myMatrix);    
  517.     SetTrackEnabled(myTextTrack, true);
  518.     
  519.     //////////
  520.     //
  521.     // edit the track media
  522.     //
  523.     //////////
  524.  
  525.     myErr = BeginMediaEdits(myMedia);
  526.     if (myErr == noErr) {
  527.         Rect                myBounds;    
  528.         short                myIndex;
  529.         TimeValue            myTypeSampleDuration;
  530.         TimeRecord            myTimeRec;
  531.         
  532.         myBounds.top = 0;
  533.         myBounds.left = 0;
  534.         myBounds.right = Fix2Long(myWidth);
  535.         myBounds.bottom = Fix2Long(myHeight);
  536.         
  537.         // determine the duration of a sample in the track of the specified type
  538.         myTypeSampleDuration = QTUtils_GetFrameDuration(myTypeTrack);
  539.                 
  540.  
  541.         for (myIndex = 0; myIndex < theNumFrames; myIndex++) {
  542.             TimeValue        myTextSampleDuration;
  543.             TimeValue        mySampleTime;
  544.             Str255            mySampleText;
  545.  
  546.             myTextSampleDuration = myTypeSampleDuration * theFrames[myIndex];
  547.             
  548.             // set the time scale of the media to that of the movie
  549.             myTimeRec.value.lo = myTextSampleDuration;
  550.             myTimeRec.value.hi = 0;
  551.             myTimeRec.scale = GetMovieTimeScale(theMovie);
  552.             ConvertTimeScale(&myTimeRec, GetMediaTimeScale(myMedia));
  553.             myTextSampleDuration = myTimeRec.value.lo;
  554.  
  555.             QTText_CopyCStringToPascal(theStrings[myIndex], mySampleText);
  556.  
  557.             // write out the new data to the media
  558.             myErr = TextMediaAddTextSample(    myHandler,
  559.                                             (Ptr)(&mySampleText[1]),
  560.                                             mySampleText[0],
  561.                                             0,
  562.                                             0,
  563.                                             0,
  564.                                             NULL,
  565.                                             NULL,
  566.                                             teCenter,
  567.                                               &myBounds,
  568.                                             dfClipToTextBox,
  569.                                             0,
  570.                                             0,
  571.                                             0,
  572.                                             NULL,
  573.                                             myTextSampleDuration,
  574.                                             &mySampleTime);
  575.         }
  576.     }
  577.  
  578.     EndMediaEdits(myMedia);
  579.     
  580.     // insert the text media into the text track
  581.     myErr = InsertMediaIntoTrack(myTextTrack, 0, 0, GetMediaDuration(myMedia), fixed1);
  582.  
  583.     //////////
  584.     //
  585.     // if requested, set the new text track as a chapter track for the track of the specified type
  586.     //
  587.     //////////
  588.     
  589.     if (isChapterTrack)
  590.         myErr = AddTrackReference(myTypeTrack, myTextTrack, kTrackReferenceChapterList, NULL);
  591.  
  592. bail:
  593.     return(myTextTrack);
  594. }
  595.  
  596.  
  597. //////////
  598. //
  599. // QTText_RemoveIndTextTrack
  600. // Remove a text track, specified by its index, from a movie.
  601. //
  602. // If theIndex is kAllTextTracks (0), remove all text tracks from the movie.
  603. //
  604. //////////
  605.  
  606. OSErr QTText_RemoveIndTextTrack (Movie theMovie, short theIndex)
  607. {
  608.     Track                myTrack = NULL;
  609.     OSErr                myErr = noErr;
  610.     
  611.     if (theIndex == kAllTextTracks) {
  612.         // remove ALL text tracks from the movie
  613.         myTrack = GetMovieIndTrackType(theMovie, 1, TextMediaType, movieTrackMediaType);
  614.         if (myTrack == NULL)
  615.             myErr = badTrackIndex;
  616.         
  617.         while (myTrack != NULL) {
  618.             QTUtils_DeleteAllReferencesToTrack(myTrack);
  619.             DisposeMovieTrack(myTrack);
  620.             myTrack = GetMovieIndTrackType(theMovie, 1, TextMediaType, movieTrackMediaType);
  621.         }
  622.     } else {
  623.         // remove ONE text track from the movie
  624.         myTrack = GetMovieIndTrackType(theMovie, theIndex, TextMediaType, movieTrackMediaType);
  625.         if (myTrack == NULL) {
  626.             myErr = badTrackIndex;
  627.         } else {
  628.             QTUtils_DeleteAllReferencesToTrack(myTrack);
  629.             DisposeMovieTrack(myTrack);
  630.         }
  631.     }
  632.  
  633.     return(myErr);
  634. }
  635.  
  636.  
  637. ///////////////////////////////////////////////////////////////////////////////////////////////////////////
  638. //
  639. // Chapter track utilities.
  640. //
  641. // Use these functions to manipulate chapter tracks in a movie.
  642. //
  643. // A chapter track is a text track that has been associated with some other track (often a video or sound
  644. // track) in such a way that the movie controller will build, display, and handle a pop-up menu of titles
  645. // of various parts of the associated track. The pop-up menu appears (space permitting) in the controller
  646. // bar. The various parts of the associated track are called the track's "chapters". When a user selects a
  647. // chapter title in the pop-up menu, the movie controller jumps to the start time of the selected chapter.
  648. //
  649. // You create the association between a text track and some other track by creating a reference from that
  650. // other track to the text track, where the reference is of type kTrackReferenceChapterList. Note that all
  651. // the chapter titles must be contained in a single text track; you specify the starting time for chapters
  652. // when you add the text to the text track by calling TextMediaAddTextSample. Note also that you need to
  653. // create the chapter association only between the text track and one other track, not between the chapter
  654. // track and all other tracks in the movie. (That "other" track must be enabled, but typically the chapter
  655. // track is not enabled.)
  656. //
  657. // The pop-up menu will disappear from the controller bar if there isn't enough space to display the menu,
  658. // the volume slider control, the step buttons, and the other controls. 
  659. //
  660. ///////////////////////////////////////////////////////////////////////////////////////////////////////////
  661.  
  662. //////////
  663. //
  664. // QTText_SetTextTrackAsChapterTrack
  665. // Set the (first) text track in the specified movie to be or not to be a chapter track
  666. // for the (first) enabled track of the specified type.
  667. //
  668. // The isChapterTrack parameter determines the final state of the text track.
  669. //
  670. //////////
  671.  
  672. OSErr QTText_SetTextTrackAsChapterTrack (WindowObject theWindowObject, OSType theType, Boolean isChapterTrack)
  673. {
  674.     ApplicationDataHdl        myAppData = NULL;
  675.     Movie                    myMovie = NULL;
  676.     MovieController            myMC = NULL;
  677.     Track                    myTypeTrack = NULL;
  678.     Track                    myTextTrack = NULL;
  679.     OSErr                    myErr = paramErr;
  680.         
  681.     // get the movie, controller, and related stuff    
  682.     myAppData = (ApplicationDataHdl)GetAppDataFromWindowObject(theWindowObject);
  683.     if (myAppData == NULL)
  684.         goto bail;
  685.         
  686.     myMovie = (**theWindowObject).fMovie;
  687.     myMC = (**theWindowObject).fController;
  688.     myTextTrack = (**myAppData).fTextTrack;
  689.     
  690.     if ((myMovie != NULL) && (myMC != NULL)) {
  691.         myTypeTrack = GetMovieIndTrackType(myMovie, 1, theType, movieTrackMediaType | movieTrackEnabledOnly);
  692.         if ((myTypeTrack != NULL) && (myTextTrack != NULL)) {
  693.         
  694.             // add or delete a track reference, as determined by the desired final state
  695.             if (isChapterTrack)
  696.                 myErr = AddTrackReference(myTypeTrack, myTextTrack, kTrackReferenceChapterList, NULL);
  697.             else
  698.                 myErr = DeleteTrackReference(myTypeTrack, kTrackReferenceChapterList, 1);
  699.                 
  700.             // tell the movie controller we've changed aspects of the movie
  701.             MCMovieChanged(myMC, myMovie);
  702.             
  703.             // stamp the movie as dirty
  704.             (**theWindowObject).fDirty = true;
  705.         }
  706.     }
  707.  
  708. bail:
  709.     return(myErr);
  710. }
  711.  
  712.  
  713. //////////
  714. //
  715. // QTText_TrackTypeHasAChapterTrack
  716. // Does the (first) enabled track of the specified type in the specified movie have a chapter track?
  717. //
  718. //////////
  719.  
  720. Boolean QTText_TrackTypeHasAChapterTrack (Movie theMovie, OSType theType)
  721. {
  722.     Track                    myTypeTrack = NULL;
  723.     Track                    myTextTrack = NULL;
  724.         
  725.     myTypeTrack = GetMovieIndTrackType(theMovie, 1, theType, movieTrackMediaType | movieTrackEnabledOnly);
  726.     if (myTypeTrack != NULL)
  727.         myTextTrack = GetTrackReference(myTypeTrack, kTrackReferenceChapterList, 1);
  728.  
  729.     return(myTextTrack != NULL);
  730. }
  731.  
  732.  
  733. //////////
  734. //
  735. // QTText_TrackHasAChapterTrack
  736. // Does the specified track have a chapter track?
  737. //
  738. //////////
  739.  
  740. Boolean QTText_TrackHasAChapterTrack (Track theTrack)
  741. {
  742.     return(GetTrackReference(theTrack, kTrackReferenceChapterList, 1) != NULL);
  743. }
  744.  
  745.  
  746. //////////
  747. //
  748. // QTText_MovieHasAChapterTrack
  749. // Does the specified movie have a chapter track? In other words, is there are least one enabled track
  750. // in the specified movie that has a chapter track associated wih it?
  751. //
  752. //////////
  753.  
  754. Boolean QTText_MovieHasAChapterTrack (Movie theMovie)
  755. {
  756.     long        myCount;
  757.     long        myTrackCount = GetMovieTrackCount(theMovie);
  758.     Track        myTrack = NULL;
  759.     Boolean        myGotChapter = false;
  760.     
  761.     for (myCount = 1; myCount <= myTrackCount; myCount++) {
  762.         myTrack = GetMovieIndTrack(theMovie, myCount);
  763.         if (GetTrackEnabled(myTrack))
  764.             myGotChapter = QTText_TrackHasAChapterTrack(myTrack);
  765.             if (myGotChapter)
  766.                 break;
  767.     }
  768.     
  769.     return(myGotChapter);
  770. }
  771.  
  772.  
  773. //////////
  774. //
  775. // QTText_GetChapterTrackForTrack
  776. // Return the first chapter track (if any) associated with the specified track.
  777. //
  778. //////////
  779.  
  780. Track QTText_GetChapterTrackForTrack (Track theTrack)
  781. {
  782.     return(GetTrackReference(theTrack, kTrackReferenceChapterList, 1));
  783. }
  784.  
  785.  
  786. //////////
  787. //
  788. // QTText_GetChapterTrackForMovie
  789. // Return the first chapter track (if any) in the specified movie.
  790. //
  791. // A movie can have more than one chapter track; if so, QuickTime uses the chapter track associated
  792. // with the first enabled track that it finds. Accordingly, this function returns the first chapter
  793. // track that we find as we iterate thru the movie's tracks.
  794. //
  795. //////////
  796.  
  797. Track QTText_GetChapterTrackForMovie (Movie theMovie)
  798. {
  799.     long        myCount;
  800.     long        myTrackCount = GetMovieTrackCount(theMovie);
  801.     Track        myTrack = NULL;
  802.     Track        myChapTrack = NULL;
  803.     
  804.     for (myCount = 1; myCount <= myTrackCount; myCount++) {
  805.         myTrack = GetMovieIndTrack(theMovie, myCount);
  806.         if (GetTrackEnabled(myTrack))
  807.             myChapTrack = QTText_GetChapterTrackForTrack(myTrack);
  808.             if (myChapTrack != NULL)
  809.                 break;
  810.     }
  811.     
  812.     return(myChapTrack);
  813. }
  814.  
  815.  
  816. //////////
  817. //
  818. // QTText_IsChapterTrack
  819. // Is the specified track a chapter track?
  820. //
  821. //////////
  822.  
  823. Boolean QTText_IsChapterTrack (Track theTrack)
  824. {
  825.     Movie                myMovie = NULL;
  826.     Track                myTrack = NULL;
  827.     long                myTrackCount = 0L;
  828.     long                myTrRefCount = 0L;
  829.     long                myTrackIndex;
  830.     long                myTrRefIndex;
  831.     Boolean                isChapTrack = false;
  832.  
  833.     myMovie = GetTrackMovie(theTrack);
  834.     if (myMovie == NULL)
  835.         goto bail;
  836.  
  837.     // a chapter track is a text track that is referred to by some other track in the movie,
  838.     // so we need to iterate thru all those tracks to see if any of them refers to the specified track
  839.     myTrackCount = GetMovieTrackCount(myMovie);
  840.     for (myTrackIndex = 1; myTrackIndex <= myTrackCount; myTrackIndex++) {
  841.         myTrack = GetMovieIndTrack(myMovie, myTrackIndex);
  842.         if ((myTrack != NULL) && (myTrack != theTrack)) {
  843.         
  844.             // iterate thru all track references of type kTrackReferenceChapterList
  845.             myTrRefCount = GetTrackReferenceCount(myTrack, kTrackReferenceChapterList);
  846.             for (myTrRefIndex = 1; myTrRefIndex <= myTrRefCount; myTrRefIndex++) {
  847.                 Track    myRefTrack = NULL;
  848.  
  849.                 myRefTrack = GetTrackReference(myTrack, kTrackReferenceChapterList, myTrRefIndex);
  850.                 if (myRefTrack == theTrack) {
  851.                     isChapTrack = true;
  852.                     goto bail;
  853.                 }
  854.             }
  855.         }
  856.     }
  857.  
  858. bail:
  859.     return(isChapTrack);
  860. }
  861.  
  862.  
  863. //////////
  864. //
  865. // QTText_GetFirstChapterTime
  866. // Return, through the theTime parameter, the starting time of the first chapter of the
  867. // specified chapter track.
  868. //
  869. // If this function encounters an error, it returns a (bogus) starting time of -1. Note that
  870. // GetTrackNextInterestingTime also returns -1 as a starting time if the search criteria
  871. // specified in the myFlags parameter are not matched by any interesting time in the movie. 
  872. //
  873. //////////
  874.  
  875. OSErr QTText_GetFirstChapterTime (Track theChapterTrack, TimeValue *theTime)
  876. {
  877.     short    myFlags = nextTimeMediaSample + nextTimeEdgeOK;    // we want the first sample in the movie
  878.     
  879.     if (theChapterTrack == NULL) {
  880.         *theTime = kBogusStartingTime;                        // a bogus time
  881.         return(invalidTrack);
  882.     }
  883.     
  884.     GetTrackNextInterestingTime(theChapterTrack, myFlags, (TimeValue)0, fixed1, theTime, NULL);
  885.     return(GetMoviesError());
  886. }
  887.  
  888.  
  889. //////////
  890. //
  891. // QTText_GetNextChapterTime
  892. // Return, through the theTime parameter, the starting time of the chapter of the
  893. // specified chapter track that immediately follows the chapter starting at the time
  894. // passed in through theTime.
  895. //
  896. //////////
  897.  
  898. OSErr QTText_GetNextChapterTime (Track theChapterTrack, TimeValue *theTime)
  899. {
  900.     short    myFlags = nextTimeMediaSample;                    // we want the next sample in the movie
  901.     
  902.     if (theChapterTrack == NULL) {
  903.         *theTime = kBogusStartingTime;                        // a bogus time
  904.         return(invalidTrack);
  905.     }
  906.     
  907.     GetTrackNextInterestingTime(theChapterTrack, myFlags, *theTime, fixed1, theTime, NULL);
  908.     return(GetMoviesError());
  909. }
  910.  
  911.  
  912. //////////
  913. //
  914. // QTText_GetIndChapterTime
  915. // Return the starting time of the chapter in the specified chapter track that has the specified index.
  916. //
  917. //////////
  918.  
  919. TimeValue QTText_GetIndChapterTime (Track theChapterTrack, long theIndex)
  920. {
  921.     long            myCount = 1;
  922.     TimeValue        myTime = kBogusStartingTime;
  923.  
  924.     if ((theChapterTrack == NULL) || (theIndex < 1))
  925.         return(myTime);
  926.  
  927.     QTText_GetFirstChapterTime(theChapterTrack, &myTime);
  928.     if (theIndex == 1)
  929.         return(myTime);
  930.         
  931.     while (myCount < theIndex) {
  932.         QTText_GetNextChapterTime(theChapterTrack, &myTime);
  933.         myCount++;
  934.     }
  935.     
  936.     return(myTime);
  937. }
  938.  
  939.  
  940. //////////
  941. //
  942. // QTText_GetIndChapterText
  943. // Return the text of the chapter in the specified chapter track that has the specified index.
  944. //
  945. // The caller is responsible for disposing of the pointer returned by this function (by calling free).
  946. //
  947. //////////
  948.  
  949. char *QTText_GetIndChapterText (Track theChapterTrack, long theIndex)
  950. {
  951.     long            myCount = 1;
  952.     long            mySize;            // size of entire handle returned, which may include appended style atoms
  953.     short            myTextSize;
  954.     TimeValue        myTime;
  955.     Handle            myHandle = NewHandleClear(0);
  956.     char            *myText = NULL;
  957.  
  958.     if ((theChapterTrack == NULL) || (theIndex < 1))
  959.         return(myText);
  960.  
  961.     if (myHandle == NULL)
  962.         return(myText);
  963.  
  964.     myTime = QTText_GetIndChapterTime(theChapterTrack, theIndex);
  965.     if (myTime != kBogusStartingTime) {
  966.     
  967.         GetMediaSample(    GetTrackMedia(theChapterTrack),
  968.                         myHandle,
  969.                         0,
  970.                         &mySize,
  971.                         TrackTimeToMediaTime(myTime, theChapterTrack),        // media time scale
  972.                         NULL,
  973.                         NULL,
  974.                         NULL,
  975.                         NULL,
  976.                         0,
  977.                         NULL,
  978.                         NULL);
  979.                     
  980.         // for text media samples, the returned handle is a 16-bit size field followed by the actual text data
  981.         myTextSize = *(short *)(*myHandle);
  982.         myText = malloc(myTextSize + 1);
  983.         BlockMove(*myHandle + sizeof(short), myText, myTextSize);
  984.         myText[myTextSize] = '\0';
  985.         
  986.         DisposeHandle(myHandle);
  987.     }
  988.  
  989.     return(myText);
  990. }
  991.  
  992.  
  993. //////////
  994. //
  995. // QTText_GetChapterCount
  996. // Return the number of chapters in the specified chapter track.
  997. //
  998. //////////
  999.  
  1000. long QTText_GetChapterCount (Track theChapterTrack)
  1001. {
  1002.     long            myCount = 0;
  1003.     TimeValue        myTime = kBogusStartingTime;
  1004.  
  1005.     if (theChapterTrack != NULL) {
  1006.         QTText_GetFirstChapterTime(theChapterTrack, &myTime);
  1007.             
  1008.         while (myTime != kBogusStartingTime) {
  1009.             myCount++;
  1010.             QTText_GetNextChapterTime(theChapterTrack, &myTime);
  1011.         }
  1012.     }
  1013.  
  1014.     return(myCount);
  1015. }
  1016.  
  1017.  
  1018. ///////////////////////////////////////////////////////////////////////////////////////////////////////////
  1019. //
  1020. // Miscellaneous utilities.
  1021. //
  1022. ///////////////////////////////////////////////////////////////////////////////////////////////////////////
  1023.  
  1024. //////////
  1025. //
  1026. // QTText_CopyCStringToPascal
  1027. // Convert the source C string to a destination Pascal string as it's copied.
  1028. //
  1029. // The destination string will be truncated to fit into a Str255 if necessary.
  1030. // If the C string pointer is NULL, the Pascal string's length is set to zero.
  1031. //
  1032. // This routine is borrowed from CGlue.c, by Nick Kledzik.
  1033. //
  1034. //////////
  1035.  
  1036. void QTText_CopyCStringToPascal (const char *theSrc, Str255 theDst)
  1037. {
  1038.     short                    myLength = 0;
  1039.     
  1040.     // handle case of overlapping strings
  1041.     if ((void *)theSrc == (void *)theDst) {
  1042.         unsigned char        *myCurDst = &theDst[1];
  1043.         unsigned char        myChar;
  1044.                 
  1045.         myChar = *(const unsigned char *)theSrc++;
  1046.         while (myChar != '\0') {
  1047.             unsigned char    myNextChar;
  1048.             
  1049.             // use myNextChar so we don't overwrite what we are about to read
  1050.             myNextChar = *(const unsigned char *)theSrc++;
  1051.             *myCurDst++ = myChar;
  1052.             myChar = myNextChar;
  1053.             
  1054.             if (++myLength >= 255)
  1055.                 break;
  1056.         }
  1057.     } else if (theSrc != NULL) {
  1058.         unsigned char        *myCurDst = &theDst[1];
  1059.         short                 myOverflow = 255;        // count down, so test it loop is faster
  1060.         register char        myTemp;
  1061.     
  1062.         // we can't do the K&R C thing of “while (*s++ = *t++)” because it will copy the trailing zero,
  1063.         // which might overrun the Pascal buffer; instead, we use a temp variable
  1064.         while ((myTemp = *theSrc++) != 0) {
  1065.             *(char *)myCurDst++ = myTemp;
  1066.                 
  1067.             if (--myOverflow <= 0)
  1068.                 break;
  1069.         }
  1070.         myLength = 255 - myOverflow;
  1071.     }
  1072.     
  1073.     // set the length of the destination Pascal string
  1074.     theDst[0] = myLength;
  1075. }
  1076.  
  1077.  
  1078. //////////
  1079. //
  1080. // QTText_UpdateMovieAndController
  1081. // Synchronize the movie and controller.
  1082. //
  1083. //////////
  1084.  
  1085. void QTText_UpdateMovieAndController (MovieController theMC)
  1086. {
  1087.     Movie        myMovie = NULL;
  1088.     Rect        myRect;
  1089.  
  1090.     myMovie = MCGetMovie(theMC);
  1091.  
  1092.     // we must make sure that we are still top-left aligned
  1093.     GetMovieBox(myMovie, &myRect);
  1094.     MacOffsetRect(&myRect, -myRect.left, -myRect.top);
  1095.     SetMovieBox(myMovie, &myRect);
  1096.  
  1097.     MCDoAction(theMC, mcActionMovieEdited, NULL);
  1098.     MCMovieChanged(theMC, myMovie);
  1099. }